﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using JasLib.Collections;
using JasLib.Extensions;
using JasLib.Interop.Win32;

namespace JasLib.Diagnostics
{
    /// <summary>
    /// Represents a Windows NT performance counter.
    /// Must be added to a <see cref="JasLib.Diagnostics.NtPerformanceCounterQueryAgent"/> in order to receive
    /// updated values.
    /// </summary>
    [System.Diagnostics.DebuggerDisplay("{NormalizedFullCounterPath}")]
    public class NtPerformanceCounter
    {
        public static IEnumerable<NtPerformanceCounter> ExpandWildCardPath(string strWildCardPath)
        {
            foreach (string strPath in PDH.PdhExpandWildCardPath(null, strWildCardPath))
                yield return new NtPerformanceCounter(strPath);
        }

        internal NtPerformanceCounterQueryAgent _agent;
        internal string _strInputCounterPath;
        internal string _strNormalizedFullCounterPath;
        internal PDH_HCOUNTER _hCounter;
        private PDH_COUNTER_INFO.Managed _counterInfo = null;

        public NtPerformanceCounter(string strInputCounterPath)
        {
            _strInputCounterPath = strInputCounterPath;
            if (_strInputCounterPath.Contains("*"))
                throw new Exception("NtPerfCounter by design only applies to a single performance counter. For wildcard support, use the static function ExpandWildCardPath.");

            PDH_STATUS status = PDH.PdhValidatePath(strInputCounterPath);
            status.TryThrowPdhException();
            return;
        }

        public NtPerformanceCounter(
            string strObjectName,
            string strInstanceName,
            string strCounterName)
            : this(PDH.PdhMakeCounterPath(strObjectName, strInstanceName, strCounterName))
        {
        }

        public override string ToString()
        {
            return _strInputCounterPath;
        }

        public NtPerformanceCounterQueryAgent Agent
        {
            get { return _agent; }
        }

        public void Detach()
        {
            _counterInfo = null;

            if (!_hCounter.IsNull)
            {
                PDH_STATUS status = PDH.PdhRemoveCounter(_hCounter);
                status.TryThrowPdhException("PdhRemoveCounter(\"{0}\") failed on handle {1}.", _strInputCounterPath, _hCounter);
                _hCounter = PDH_HCOUNTER.NULL;
            }

            /// NOTE: Do NOT clear the counter path; it keeps the counter reusable.
            //_strInputCounterPath = null;

            _agent = null;
            return;
        }

        private void ReloadInfo()
        {
            if (_counterInfo == null)
            {
                PDH.PdhGetCounterInfo(_hCounter, true, out _counterInfo);
            }
        }

        private void InvalidateInfo()
        {
            _counterInfo = null;
            _strNormalizedFullCounterPath = null;
            return;
        }

        public string InputCounterPath
        {
            get { return _strInputCounterPath; }
        }

        public string NormalizedFullCounterPath
        {
            get
            {
                if (_strNormalizedFullCounterPath == null)
                {
                    ReloadInfo();
                    _strNormalizedFullCounterPath = PDH.PdhMakeCounterPath(ref _counterInfo.CounterPath);
                }
                return _strNormalizedFullCounterPath;
            }
        }

        public int ScaleFactor
        {
            get
            {
                ReloadInfo();
                return _counterInfo.lScale;
            }
            set
            {
                PDH_STATUS status = PDH.PdhSetCounterScaleFactor(_hCounter, value);
                status.TryThrowPdhException();
                InvalidateInfo();
                return;
            }
        }

        public string ObjectName
        {
            get
            {
                ReloadInfo();
                return _counterInfo.CounterPath.szObjectName;
            }
        }

        public string InstanceName
        {
            get
            {
                ReloadInfo();
                return _counterInfo.CounterPath.szInstanceName;
            }
        }

        public string CounterName
        {
            get
            {
                ReloadInfo();
                return _counterInfo.CounterPath.szCounterName;
            }
        }

        public string Description
        {
            get
            {
                ReloadInfo();
                return _counterInfo.szExplainText;
            }
        }

        public double DoubleValue
        {
            get
            {
                PerformanceCounterType type;
                PDH_FMT_COUNTERVALUE value;
                PDH_STATUS status = PDH.PdhGetFormattedCounterValue(
                    _hCounter,
                    PdhFormat.PDH_FMT_DOUBLE | PdhFormat.PDH_FMT_NOSCALE,
                    out type,
                    out value);
                status.TryThrowPdhException("PdhGetFormattedCounterValue(hCounter = {0}) failed.", _hCounter);
                value.CStatus.TryThrowPdhException();
                return value._valueUnion.doubleValue;
            }
        }

        public bool TryGetDoubleValue(out double outputValue)
        {
            PerformanceCounterType type;
            PDH_FMT_COUNTERVALUE value;
            PDH_STATUS status = PDH.PdhGetFormattedCounterValue(
                _hCounter,
                PdhFormat.PDH_FMT_DOUBLE | PdhFormat.PDH_FMT_NOSCALE,
                out type,
                out value);

            if (status != 0 || value.CStatus != 0)
            {
                outputValue = 0.0;
                return false;
            }
            else
            {
                outputValue = value._valueUnion.doubleValue;
                return true;
            }
        }

        public int Int32Value
        {
            get
            {
                PerformanceCounterType type;
                PDH_FMT_COUNTERVALUE value;
                PDH_STATUS status = PDH.PdhGetFormattedCounterValue(
                    _hCounter,
                    PdhFormat.PDH_FMT_LONG | PdhFormat.PDH_FMT_NOSCALE | PdhFormat.PDH_FMT_NOCAP100,
                    out type,
                    out value);
                status.TryThrowPdhException("PdhGetFormattedCounterValue(hCounter = {0}) failed.", _hCounter);
                value.CStatus.TryThrowPdhException();
                return value._valueUnion.longValue;
            }
        }

        public ulong UInt64Value
        {
            get
            {
                PerformanceCounterType type;
                PDH_FMT_COUNTERVALUE value;
                PDH_STATUS status = PDH.PdhGetFormattedCounterValue(
                    _hCounter,
                    PdhFormat.PDH_FMT_LARGE | PdhFormat.PDH_FMT_NOSCALE | PdhFormat.PDH_FMT_NOCAP100,
                    out type,
                    out value);
                status.TryThrowPdhException("PdhGetFormattedCounterValue(hCounter = {0}) failed.", _hCounter);
                value.CStatus.TryThrowPdhException();
                return value._valueUnion.largeValue;
            }
        }
    }

    [System.Diagnostics.DebuggerDisplay("{_counters.Count} counter(s) active")]
    public class NtPerformanceCounterQueryAgent : FinalizedDisposableObject
    {
        public static IEnumerable<string> EnumLocalObjects()
        {
            return PDH.PdhEnumObjects();
        }

        public static void EnumLocalObjectInstancesAndCounters(
            string strObjectName,
            out string[] instances,
            out string[] counters)
        {
            PDH.PdhEnumObjectItems(null, null, strObjectName, out counters, out instances);
            return;
        }

        public class NtPerformanceCounterCollection : SimplifiedCollectionBase<NtPerformanceCounter>
        {
            private NtPerformanceCounterQueryAgent _agent = null;
            private readonly HashSet<NtPerformanceCounter> _counters = new HashSet<NtPerformanceCounter>();

            public NtPerformanceCounterCollection(NtPerformanceCounterQueryAgent agent)
            {
                if (agent == null)
                    throw new ArgumentNullException("agent");
                _agent = agent;
                return;
            }

            public override void Add(NtPerformanceCounter counter)
            {
                try
                {
                    /// Steal it from the agent it was using before.
                    if (!counter._agent.In(null, _agent))
                        counter._agent._counters.Remove(counter);

                    PDH_STATUS status = PDH.PdhAddCounter(_agent._hQuery, counter._strInputCounterPath, 0, out counter._hCounter);
                    status.TryThrowPdhException("PdhAddCounter(hQuery = {0}) failed.", _agent._hQuery);

                    _counters.Add(counter);
                }
                catch
                {
                    counter.Detach();
                    throw;
                }
            }

            public override bool Remove(NtPerformanceCounter item)
            {
                bool bExisted = _counters.Remove(item);
                if (bExisted)
                    item.Detach();
                return bExisted;
            }

            public override int Count
            {
                get { return _counters.Count; }
            }

            public override bool Contains(NtPerformanceCounter item)
            {
                return _counters.Contains(item);
            }

            public override void Clear()
            {
                foreach (NtPerformanceCounter counter in _counters)
                    counter.Detach();
                _counters.Clear();
                return;
            }

            public override IEnumerator<NtPerformanceCounter> GetEnumerator()
            {
                return _counters.GetEnumerator();
            }
        }

        protected readonly NtPerformanceCounterCollection _counters;
        public NtPerformanceCounterCollection Counters
        {
            get { return _counters; }
        }

        protected PDH_HQUERY _hQuery;

        public NtPerformanceCounterQueryAgent()
        {
            PDH_STATUS status = PDH.PdhOpenQuery(null, 0, out _hQuery);
            status.TryThrowPdhException();

            _counters = new NtPerformanceCounterCollection(this);
            return;
        }

        protected override void Dispose(bool bFinalizer)
        {
            Counters.Clear();

            if (!_hQuery.IsNull)
            {
                PDH_STATUS status = PDH.PdhCloseQuery(_hQuery);
                status.TryThrowPdhException();
                _hQuery = PDH_HQUERY.NULL;
            }
            return;
        }

        public void Update()
        {
            if (_counters.Count > 0 && !_hQuery.IsNull)
            {
                PDH_STATUS status = PDH.PdhCollectQueryData(_hQuery);
                status.TryThrowPdhException("PdhCollectQueryData(hQuery = {0}) failed.", _hQuery);
            }
            return;
        }
    }
}